﻿using System;
using System.Text;
using System.Security.Cryptography;
using System.IO;

namespace Nb.Common.Encrypt
{
    /// <summary>
    /// 非对称加密助手类
    /// RSA 是常用的非对称加密算法。最近使用时却出现了“不正确的长度”的异常，研究发现是由于待加密的数据超长所致。
    /// .NET Framework 中提供的 RSA 算法规定：
    /// 待加密的字节数不能超过密钥的长度值除以 8 再减去 11（即：RSACryptoServiceProvider.KeySize / 8 - 11），而加密后得到密文的字节数，正好是密钥的长度值除以 8（即：RSACryptoServiceProvider.KeySize / 8）。
    /// 如果要加密较长的数据，则可以采用分段加解密的方式
    /// </summary>
    public class MyAsymmetricCryptoHelper
    {
        #region 私有字段

        private bool _doOAEPPadding = false;
        private CspParameters _cspPrarmeters;
        private string _containerName;
        private RSACryptoServiceProvider _rsa;
        private RSAParameters _rsaParameters;
        private string _publicKey;
        private string _privateAndPublicKey;

        #endregion

        #region 属性

        public string PublicKey
        {
            get { return _publicKey; }
            set { _publicKey = value; }
        }

        public string PrivateAndPublicKey
        {
            get { return _privateAndPublicKey; }
            set { _privateAndPublicKey = value; }
        }

        public bool DoOAEPPadding
        {
            get { return _doOAEPPadding; }
            set { _doOAEPPadding = value; }
        }

        public string ContainerName
        {
            get { return _containerName; }
            set { _containerName = value; }
        }

        public RSAParameters RsaParameters
        {
            get { return _rsaParameters; }
            set { _rsaParameters = value; }
        }

        public RSACryptoServiceProvider Rsa
        {
            get { return _rsa; }
            set { _rsa = value; }
        }

        #endregion

        //可以用以下两种方法之一提取密钥信息
        //PublicKey属性，它返回密钥信息的 XML 表示形式。
        //RsaParameters属性，它返回 RSAParameters 结构以保存密钥信息。
        //默认的rsa有两个方法都接受布尔值，该值指示是只返回公钥信息还是同时返回公钥和私钥信息。
        //通过使用 ImportParameters 方法，可以将 RSACryptoServiceProvider 类初始化为 RSAParameters 结构的值。
        //千万不要将不对称私钥逐字存储（或者说以明文形式存储）在本地计算机上。！！
        //如果需要存储私钥，则应使用密钥容器。

        #region 构造函数

        public MyAsymmetricCryptoHelper() : this("myContainer") { }

        public MyAsymmetricCryptoHelper(string containerName)
        {
            if (string.IsNullOrEmpty(containerName))
            {
                containerName = "myContainer";
            }
            _containerName = containerName;
            _cspPrarmeters = new CspParameters();
            _cspPrarmeters.KeyContainerName = _containerName;
            _rsa = new RSACryptoServiceProvider(_cspPrarmeters);
            _publicKey = _rsa.ToXmlString(false);
            _privateAndPublicKey = _rsa.ToXmlString(true);
        }

        #endregion

        #region 公开方法

        //非对称加密算法
        public byte[] RSAEncrypt(byte[] inputBytes)
        {
            int maxBlockSize = _rsa.KeySize / 8 - 11;    //加密块最大长度限制
            if (inputBytes.Length <= maxBlockSize)
            {
                return _rsa.Encrypt(inputBytes, _doOAEPPadding);
            }

            using (MemoryStream plaiStream = new MemoryStream(inputBytes))
            using (MemoryStream crypStream = new MemoryStream())
            {
                Byte[] buffer = new Byte[maxBlockSize];
                int blockSize = plaiStream.Read(buffer, 0, maxBlockSize);

                while (blockSize > 0)
                {
                    Byte[] toEncrypt = new Byte[blockSize];
                    Array.Copy(buffer, 0, toEncrypt, 0, blockSize);

                    Byte[] cryptograph = _rsa.Encrypt(toEncrypt, _doOAEPPadding);
                    crypStream.Write(cryptograph, 0, cryptograph.Length);
                    blockSize = plaiStream.Read(buffer, 0, maxBlockSize);
                }
                return crypStream.ToArray();
            }
        }

        public byte[] RSAEncrypt(byte[] inputBytes, bool doOAEPPadding)
        {
            _doOAEPPadding = doOAEPPadding;
            return RSAEncrypt(inputBytes);
        }

        public string RSAEncrypt(string inputString)
        {
            byte[] bytes = RSAEncrypt(Encoding.Unicode.GetBytes(inputString));
            //StringBuilder sb = new StringBuilder();
            //for (int i = 0; i < bytes.Length; i++)
            //{
            //    sb.Append(bytes[i]);
            //}
            //return sb.ToString();
            ////string temp = Encoding.Unicode.GetString(bytes);
            ////return temp;
            return Convert.ToBase64String(bytes);
        }

        public string RSAEncrypt(string inputString, bool doOAEPPadding)
        {
            _doOAEPPadding = doOAEPPadding;
            return RSAEncrypt(inputString);
        }

        //非对称解密算法
        public byte[] RSADecrypt(byte[] inputBytes)
        {
            int maxBlockSize = _rsa.KeySize / 8;    //解密块最大长度限制

            if (inputBytes.Length <= maxBlockSize)
            {
                return _rsa.Decrypt(inputBytes, _doOAEPPadding);
            }

            using (MemoryStream crypStream = new MemoryStream(inputBytes))
            using (MemoryStream plaiStream = new MemoryStream())
            {
                Byte[] buffer = new Byte[maxBlockSize];
                int blockSize = crypStream.Read(buffer, 0, maxBlockSize);

                while (blockSize > 0)
                {
                    Byte[] toDecrypt = new Byte[blockSize];
                    Array.Copy(buffer, 0, toDecrypt, 0, blockSize);
                    Byte[] Plaintext = _rsa.Decrypt(toDecrypt, _doOAEPPadding);
                    plaiStream.Write(Plaintext, 0, Plaintext.Length);

                    blockSize = crypStream.Read(buffer, 0, maxBlockSize);
                }

                return plaiStream.ToArray();
            }
        }

        public byte[] RSADecrypt(byte[] inputBytes, bool doOAEPPadding)
        {
            _doOAEPPadding = doOAEPPadding;
            return RSADecrypt(inputBytes);
        }

        public string RSADecrypt(string inputString)
        {
            byte[] inputBytes = Convert.FromBase64String(inputString);
            return Encoding.Unicode.GetString(RSADecrypt(inputBytes));
        }

        public string RSADecrypt(string inputString, bool doOAEPPadding)
        {
            _doOAEPPadding = doOAEPPadding;
            return RSADecrypt(inputString);
        }

        ////格外准备的利用外部rsa的publicKey解密的方法
        //public string RSAEncryptByPublicKey(string inputString, string xmlPublicKey)
        //{
        //    //rsa为全新的一个rsa实例，与上述所有变量无关
        //    RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
        //    rsa.FromXmlString(xmlPublicKey);
        //    //开始加密            
        //    byte[] inputBytes = Encoding.Unicode.GetBytes(inputString);
        //    return Convert.ToBase64String(rsa.Encrypt(inputBytes, _doOAEPPadding));
        //}

        #endregion

        #region SignData

        /// <summary>
        /// 此方法不属于该类职责，已经被声明废弃！！！
        /// MySignatureHelper中有
        /// </summary>
        /// <param name="inputString"></param>
        /// <returns></returns>
        [Obsolete("此方法不属于该类职责，已经被声明废弃！请使用MySignatureHelper进行签名")]
        public string SignData(string inputString)
        {
            //MySignatureHelper sHelper = new MySignatureHelper(this.Rsa);
            //return sHelper.SignData(inputString);
            string temp = MySignatureHelper.Instance.SignData(inputString);
            return temp;

        }

        #endregion

        #region 公开静态方法        
        public static string EncryptByPrivateKey(string inputString, string xmlPrivateKey)
        {
            bool doOAEPPadding = false;
            ////rsa为全新的一个rsa实例，与上述所有变量无关
            //CspParameters cspPrarmeters = new CspParameters();
            //cspPrarmeters.KeyContainerName = "myContainer";
            //RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(cspPrarmeters);
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            rsa.FromXmlString(xmlPrivateKey);

            //用公钥加密    
            byte[] inputBytes = Encoding.Unicode.GetBytes(inputString);

            int MaxBlockSize = rsa.KeySize / 8 - 11;    //加密块最大长度限制
            if (inputBytes.Length <= MaxBlockSize)
            {
                return Convert.ToBase64String(rsa.Encrypt(inputBytes, doOAEPPadding));
            }

            using (MemoryStream PlaiStream = new MemoryStream(inputBytes))
            using (MemoryStream CrypStream = new MemoryStream())
            {
                Byte[] Buffer = new Byte[MaxBlockSize];
                int BlockSize = PlaiStream.Read(Buffer, 0, MaxBlockSize);

                while (BlockSize > 0)
                {
                    Byte[] ToEncrypt = new Byte[BlockSize];
                    Array.Copy(Buffer, 0, ToEncrypt, 0, BlockSize);

                    Byte[] Cryptograph = rsa.Encrypt(ToEncrypt, doOAEPPadding);
                    CrypStream.Write(Cryptograph, 0, Cryptograph.Length);
                    BlockSize = PlaiStream.Read(Buffer, 0, MaxBlockSize);
                }
                return Convert.ToBase64String(CrypStream.ToArray());
            }

        }
        public static string DecryptByPrivateKey(string inputString, string xmlPrivateKey)
        {
            bool doOAEPPadding = false;
            ////rsa为全新的一个rsa实例，与上述所有变量无关
            //CspParameters cspPrarmeters = new CspParameters();
            //cspPrarmeters.KeyContainerName = "myContainer";
            //RSACryptoServiceProvider rsa = new RSACryptoServiceProvider(cspPrarmeters);
            RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
            rsa.FromXmlString(xmlPrivateKey);

            //用公钥加密    
            byte[] inputBytes = Encoding.Unicode.GetBytes(inputString);
            int maxBlockSize = rsa.KeySize / 8;    //解密块最大长度限制

            if (inputBytes.Length <= maxBlockSize)
            {
                return Convert.ToBase64String(rsa.Decrypt(inputBytes, doOAEPPadding));
            }

            using (MemoryStream crypStream = new MemoryStream(inputBytes))
            using (MemoryStream plaiStream = new MemoryStream())
            {
                Byte[] buffer = new Byte[maxBlockSize];
                int blockSize = crypStream.Read(buffer, 0, maxBlockSize);

                while (blockSize > 0)
                {
                    Byte[] toDecrypt = new Byte[blockSize];
                    Array.Copy(buffer, 0, toDecrypt, 0, blockSize);
                    Byte[] Plaintext = rsa.Decrypt(toDecrypt, doOAEPPadding);
                    plaiStream.Write(Plaintext, 0, Plaintext.Length);

                    blockSize = crypStream.Read(buffer, 0, maxBlockSize);
                }

                return Convert.ToBase64String(plaiStream.ToArray());
            }
        }
        #endregion
    }
}
